package com.ikingtech.platform.service.system.dict.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.dict.api.DictApi;
import com.ikingtech.framework.sdk.dict.model.DictDTO;
import com.ikingtech.framework.sdk.dict.model.DictQueryParamDTO;
import com.ikingtech.framework.sdk.enums.system.dictionary.DictEnum;
import com.ikingtech.framework.sdk.enums.system.dictionary.DictItemEnum;
import com.ikingtech.framework.sdk.enums.system.dictionary.DictTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.dict.entity.DictDO;
import com.ikingtech.platform.service.system.dict.entity.DictItemDO;
import com.ikingtech.platform.service.system.dict.exception.DictExceptionInfo;
import com.ikingtech.platform.service.system.dict.service.repository.DictItemRepository;
import com.ikingtech.platform.service.system.dict.service.repository.DictRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 系统管理-字典管理
 * <p>1. 字典数据需按租户隔离
 *
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/dict", name = "系统管理-字典管理", description = "系统管理-字典管理")
public class DictController implements DictApi {

    private final DictRepository repo;

    private final DictItemRepository itemRepo;

    @Override
    @OperationLog(value = "新增字典", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(DictDTO dict) {
        if (this.repo.exists(Wrappers.<DictDO>lambdaQuery()
                .eq(DictDO::getName, dict.getName())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DictDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DictExceptionInfo.DUPLICATE_DICT_NAME);
        }
        if (this.repo.exists(Wrappers.<DictDO>lambdaQuery()
                .eq(DictDO::getCode, dict.getCode())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DictDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DictExceptionInfo.DUPLICATE_DICT_CODE);
        }
        DictDO entity = Tools.Bean.copy(dict, DictDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setTenantCode(Me.tenantCode());
        entity.setCode(Tools.Str.isBlank(dict.getCode()) ? entity.getId() : dict.getCode());
        entity.setType(DictTypeEnum.BUSINESS.name());
        this.repo.save(entity);
        return R.ok(entity.getId());
    }

    @Override
    @OperationLog(value = "删除字典")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        DictDO entity = this.repo.getById(id);
        if (null == entity) {
            return R.ok();
        }
        if (DictTypeEnum.SYSTEM.name().equals(entity.getType())) {
            throw new FrameworkException(DictExceptionInfo.DELETE_SYSTEM_DICT_IS_NOT_ALLOWED);
        }
        this.repo.removeById(id);
        this.itemRepo.remove(Wrappers.<DictItemDO>lambdaQuery()
                .and(Tools.Str.isBlank(entity.getTenantCode()), wrapper -> wrapper.eq(DictItemDO::getTenantCode, Tools.Str.EMPTY).or().isNull(DictItemDO::getTenantCode))
                .eq(Tools.Str.isNotBlank(entity.getTenantCode()), DictItemDO::getTenantCode, entity.getTenantCode())
                .eq(DictItemDO::getDictCode, entity.getCode()).eq(DictItemDO::getTenantCode, Me.tenantCode()));
        return R.ok();
    }

    @Override
    @OperationLog(value = "更新字典")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(DictDTO dict) {
        if (!this.repo.exists(Wrappers.<DictDO>lambdaQuery().eq(DictDO::getId, dict.getId()))) {
            throw new FrameworkException(DictExceptionInfo.DICT_NOT_FOUND);
        }
        if (this.repo.exists(Wrappers.<DictDO>lambdaQuery()
                .ne(DictDO::getId, dict.getId())
                .eq(DictDO::getName, dict.getName())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DictDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DictExceptionInfo.DUPLICATE_DICT_NAME);
        }
        if (this.repo.exists(Wrappers.<DictDO>lambdaQuery()
                .ne(DictDO::getId, dict.getId())
                .eq(DictDO::getCode, dict.getCode())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DictDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DictExceptionInfo.DUPLICATE_DICT_CODE);
        }
        DictDO entity = Tools.Bean.copy(dict, DictDO.class);
        entity.setType(DictTypeEnum.BUSINESS.name());
        this.repo.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<DictDTO>> page(DictQueryParamDTO queryParam) {
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()),
                        DictRepository.createWrapper(queryParam, Me.tenantCode())))
                .convert(this::modelConvert));
    }

    @Override
    public R<List<DictDTO>> all() {
        // 根据当前用户的租户标识查询
        return R.ok(this.modelConvert(this.repo.list(Wrappers.<DictDO>lambdaQuery()
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DictDO::getTenantCode, Me.tenantCode())
                .orderByDesc(DictDO::getCreateTime))));
    }

    @Override
    public R<DictDTO> detail(String id) {
        return R.ok(this.modelConvert(this.repo.getById(id)));
    }

    private List<DictDTO> modelConvert(List<DictDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }

    private DictDTO modelConvert(DictDO entity) {
        DictDTO dict = Tools.Bean.copy(entity, DictDTO.class);
        if (null != dict.getType()) {
            dict.setTypeName(DictTypeEnum.valueOf(entity.getType()).description);
        }
        return dict;
    }

    @EventListener()
    public void systemInitEventListener(SystemInitEvent initEvent) {
        List<DictDO> existDictEntities = this.repo.list(Wrappers.<DictDO>lambdaQuery()
                .and(wrapper -> wrapper.eq(DictDO::getTenantCode, Tools.Str.EMPTY).or().isNull(DictDO::getTenantCode))
                .eq(DictDO::getType, DictTypeEnum.SYSTEM.name()));
        Map<String, String> existDictMap = Tools.Coll.convertMap(existDictEntities, DictDO::getCode, DictDO::getId);
        List<DictDO> dictEntities = new ArrayList<>();
        for (DictEnum value : DictEnum.values()) {
            if (!existDictMap.containsKey(value.name())) {
                dictEntities.add(this.createDict(value, Tools.Str.EMPTY));
            }
            existDictMap.remove(value.name());
        }
        if (Tools.Coll.isNotBlank(dictEntities)) {
            this.repo.saveBatch(dictEntities);
        }
        if (Tools.Coll.isNotBlankMap(existDictMap)) {
            this.repo.remove(Wrappers.<DictDO>lambdaQuery().in(DictDO::getCode, existDictMap.keySet()));
        }
        List<DictItemDO> existDictItemEntities = this.itemRepo.list(Wrappers.<DictItemDO>lambdaQuery()
                .and(wrapper -> wrapper.eq(DictItemDO::getTenantCode, Tools.Str.EMPTY).or().isNull(DictItemDO::getTenantCode))
                .eq(DictItemDO::getPreset, true));
        Map<String, String> existDictItemMap = Tools.Coll.convertMap(existDictItemEntities, DictItemDO::getValue, DictItemDO::getId);
        List<DictItemDO> dictItemEntities = new ArrayList<>();
        for (DictItemEnum value : DictItemEnum.values()) {
            if (!existDictItemMap.containsKey(value.name())) {
                dictItemEntities.add(this.createDictItem(value, Tools.Str.EMPTY));
            }
            existDictItemMap.remove(value.name());
        }
        if (Tools.Coll.isNotBlank(dictItemEntities)) {
            this.itemRepo.saveBatch(dictItemEntities);
        }
        if (Tools.Coll.isNotBlankMap(existDictItemMap)) {
            this.itemRepo.remove(Wrappers.<DictItemDO>lambdaQuery().in(DictItemDO::getValue, existDictItemMap.keySet()));
        }

        if (Tools.Coll.isNotBlank(initEvent.getTenantCodes())) {
            List<DictDO> existTenantDictEntities = this.repo.list(Wrappers.<DictDO>lambdaQuery()
                    .in(DictDO::getTenantCode, initEvent.getTenantCodes())
                    .eq(DictDO::getType, DictTypeEnum.SYSTEM.name()));
            Map<String, List<DictDO>> existTenantDictMap = Tools.Coll.convertGroup(existTenantDictEntities, DictDO::getTenantCode);
            List<DictItemDO> existTenantDictItemEntities = this.itemRepo.list(Wrappers.<DictItemDO>lambdaQuery()
                    .in(DictItemDO::getTenantCode, initEvent.getTenantCodes())
                    .eq(DictItemDO::getPreset, true));
            Map<String, List<DictItemDO>> existTenantDictItemMap = Tools.Coll.convertGroup(existTenantDictItemEntities, DictItemDO::getTenantCode);
            List<DictDO> tenantDictEntities = new ArrayList<>();
            List<String> dictIdForDelete = new ArrayList<>();
            List<DictItemDO> tenantDictItemEntities = new ArrayList<>();
            List<String> dictItemIdForDelete = new ArrayList<>();
            initEvent.getTenantCodes().forEach(tenantCode -> {
                List<DictDO> currentTenantDictEntities = new ArrayList<>();
                List<DictDO> currentTenantExistDictEntities = existTenantDictMap.get(tenantCode);
                Map<String, String> currentTenantExistDictMap = Tools.Coll.convertMap(currentTenantExistDictEntities, DictDO::getCode, DictDO::getId);
                if (!existTenantDictMap.containsKey(tenantCode)) {
                    currentTenantDictEntities = Tools.Array.convertList(DictEnum.values(), value -> this.createDict(value, tenantCode));
                } else {
                    for (DictEnum value : DictEnum.values()) {
                        if (!currentTenantExistDictMap.containsKey(value.name())) {
                            currentTenantDictEntities.add(this.createDict(value, tenantCode));
                        }
                        currentTenantExistDictMap.remove(value.name());
                    }
                }
                existTenantDictMap.remove(tenantCode);
                tenantDictEntities.addAll(currentTenantDictEntities);
                if (Tools.Coll.isNotBlankMap(currentTenantExistDictMap)) {
                    dictIdForDelete.addAll(currentTenantExistDictMap.values());
                }
                List<DictItemDO> currentTenantDictItemEntities = new ArrayList<>();
                List<DictItemDO> currentTenantExistDictItemEntities = existTenantDictItemMap.get(tenantCode);
                Map<String, String> currentTenantExistDictItemMap = Tools.Coll.convertMap(currentTenantExistDictItemEntities, DictItemDO::getValue, DictItemDO::getId);
                if (!existTenantDictItemMap.containsKey(tenantCode)) {
                    currentTenantDictItemEntities = Tools.Array.convertList(DictItemEnum.values(),
                            value -> this.createDictItem(value, tenantCode));
                } else {
                    for (DictItemEnum value : DictItemEnum.values()) {
                        if (!currentTenantExistDictItemMap.containsKey(value.name())) {
                            currentTenantDictItemEntities.add(this.createDictItem(value, tenantCode));
                        }
                        currentTenantExistDictItemMap.remove(value.name());
                    }
                }
                existTenantDictItemMap.remove(tenantCode);
                tenantDictItemEntities.addAll(currentTenantDictItemEntities);
                if (Tools.Coll.isNotBlankMap(currentTenantExistDictItemMap)) {
                    dictItemIdForDelete.addAll(currentTenantExistDictItemMap.values());
                }
            });
            if (Tools.Coll.isNotBlankMap(existTenantDictMap)) {
                dictIdForDelete.addAll(Tools.Coll.flatMap(existTenantDictMap.values(), value -> Tools.Coll.convertList(value, DictDO::getId), Collection::stream));
            }
            if (Tools.Coll.isNotBlank(tenantDictEntities)) {
                this.repo.saveBatch(tenantDictEntities);
            }
            if (Tools.Coll.isNotBlank(dictIdForDelete)) {
                this.repo.remove(Wrappers.<DictDO>lambdaQuery().in(DictDO::getId, dictIdForDelete));
            }

            if (Tools.Coll.isNotBlankMap(existTenantDictItemMap)) {
                dictItemIdForDelete.addAll(Tools.Coll.flatMap(existTenantDictItemMap.values(), value -> Tools.Coll.convertList(value, DictItemDO::getId), Collection::stream));
            }
            if (Tools.Coll.isNotBlank(tenantDictItemEntities)) {
                this.itemRepo.saveBatch(tenantDictItemEntities);
            }
            if (Tools.Coll.isNotBlank(dictItemIdForDelete)) {
                this.itemRepo.remove(Wrappers.<DictItemDO>lambdaQuery().in(DictItemDO::getId, dictItemIdForDelete));
            }
        }
    }

    @EventListener
    public void tenantInitEventListener(TenantInitEvent event) {
        List<DictDO> existTenantDictEntities = this.repo.list(Wrappers.<DictDO>lambdaQuery()
                .eq(DictDO::getTenantCode, event.getCode())
                .eq(DictDO::getType, DictTypeEnum.SYSTEM.name()));
        Map<String, String> existTenantDictMap = Tools.Coll.convertMap(existTenantDictEntities, DictDO::getCode, DictDO::getId);
        List<DictDO> entities = new ArrayList<>();
        for (DictEnum value : DictEnum.values()) {
            if (!existTenantDictMap.containsKey(value.name())) {
                entities.add(this.createDict(value, event.getCode()));
            }
            existTenantDictMap.remove(value.name());
        }
        if (Tools.Coll.isNotBlank(entities)) {
            this.repo.saveBatch(entities);
        }
        if (Tools.Coll.isNotBlankMap(existTenantDictMap)) {
            this.repo.remove(Wrappers.<DictDO>lambdaQuery().in(DictDO::getCode, existTenantDictMap.keySet()));
        }
        List<DictItemDO> existTenantDictItemEntities = this.itemRepo.list(Wrappers.<DictItemDO>lambdaQuery()
                .eq(DictItemDO::getTenantCode, event.getCode()));
        Map<String, String> existTenantDictItemMap = Tools.Coll.convertMap(existTenantDictItemEntities, DictItemDO::getValue, DictItemDO::getId);
        List<DictItemDO> itemEntities = new ArrayList<>();
        for (DictItemEnum value : DictItemEnum.values()) {
            if (!existTenantDictItemMap.containsKey(value.name())) {
                itemEntities.add(this.createDictItem(value, event.getCode()));
            }
            existTenantDictItemMap.remove(value.name());
        }
        if (Tools.Coll.isNotBlank(itemEntities)) {
            this.itemRepo.saveBatch(itemEntities);
        }
        if (Tools.Coll.isNotBlankMap(existTenantDictItemMap)) {
            this.itemRepo.remove(Wrappers.<DictItemDO>lambdaQuery().in(DictItemDO::getId, existTenantDictItemMap.values()));
        }
    }

    @EventListener
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        this.repo.remove(Wrappers.<DictDO>query().lambda()
                .eq(DictDO::getTenantCode, event.getCode()));
        this.itemRepo.remove(Wrappers.<DictItemDO>query().lambda()
                .eq(DictItemDO::getTenantCode, event.getCode()));
    }

    private DictDO createDict(DictEnum value, String tenantCode) {
        DictDO entity = new DictDO();
        entity.setId(Tools.Id.uuid());
        entity.setName(value.description);
        entity.setRemark(value.description);
        entity.setCode(value.name());
        entity.setType(DictTypeEnum.SYSTEM.name());
        entity.setTenantCode(tenantCode);
        return entity;
    }

    private DictItemDO createDictItem(DictItemEnum value, String tenantCode) {
        DictItemDO entity = new DictItemDO();
        entity.setId(Tools.Id.uuid());
        entity.setLabel(value.description);
        entity.setValue(value.name());
        entity.setRemark(value.description);
        entity.setDictCode(value.dict.name());
        entity.setTenantCode(tenantCode);
        entity.setFullPath(value.name());
        entity.setPreset(true);
        return entity;
    }
}
